问题
泛型指声明中具有一个或者多个类型参数的类或者接口。每种泛型定义一组参数化的类型,构成格式为:类或者接口的名称,接着用尖括号<>把实际类型参数列表括起来。例如List
,是一个参数化的类型,表示元素类型为String的列表,(String是与形式类型参数E相对应的实际类型参数)。 每个泛型都定义一个原生态类型,即不带任何实际类型参数的泛型名称,如List
对应的原生态类型是List。 那么,是否应该使用原生态类型?
解决方案
泛型的优势在于:1. 当实例对象插入到集合中时会进行类型判断,可以将类型不匹配问题在编译时期就可以知道,而不至于等到运行时出现ClassCastException;2. 由于集合已经知道装载的是哪种类型的实例对象,因此从集合中取出对象时就无需再进行转换;
使用原生态类型虽然也是合法的,但是不应该这样做,因为这会失去泛型在安全性和表述性方面的所有优势。原生态类型存在的理由是因为老代码的移植兼容性。
List nameList = new ArrayList(); nameList.add(1); nameList.add("test");
如上例代码,如果我们在声明泛型的时候使用了原生态类型的泛型,但是在添加数据元素一次添加了Integer,一次添加了String,编译却能通过,但是很显然会出现运行时异常。因此,如果使用原生类型,就失掉了泛型在安全性和表述性方面的所有优势。
那么如何使用泛型以插入任意对象?
虽然不应该在新代码中使用想List这样原生态类型,使用参数化的类型以允许插入任意对象,如
List<Object>
,这还是可以的。原生态类型List和参数化类型
List<Object>
之间到底有什么区别呢?不严格的说,前者逃避了泛型的检查,后者明确告诉编译器,它能够持有任意类型的对象。虽然你可以将
List<String>
传递给类型List
的参数,但是不能将它传给类型List<Object>
的参数。这是因为泛型有子类型化规则,List<String>
是原生态类型List的一个子类型,而不是参数化类型List<Object>
的子类型。因此,如果使用像List
这样的原生态类型,就会丢失类型安全性,但是如果使用像List<Object>
这样的参数化类型,则不会丢失类型安全性。例如下例代码:public static void main(String[] args) { List<String> strList = new ArrayList<String>(); unsafeAdd(strList, new Integer(20)); String str = strList.get(0); } private static void unsafeAdd(List list, Object o){ list.add(o); }
上例代码中使用了原生态泛型,编译可以通过,但是很显然由于添加了Integer类型,在get集合元素的时候会出现ClassCastException。但是如果使用List
无限制通配符
要使用泛型,但不确定或者不关心实际的类型参数,就可以使用一个问号替代。例如,泛型Set 的无限制通配符类型为 Set< ? >。这是最普通的参数化Set集合,可以持有任何集合。通配符类型是安全的,原生态类型不安全。由于可以将任何元素放进原生态类型的集合中,因此很容易破坏改集合的类型约束条件。
结论
- 总之,使用原生态类型会在运行时导致异常,因此不要再新代码中使用。但就有人问既然这么不推荐使用,为什么还要存在原生态类型呢?原生态类型只是为了与引入泛型之前的遗留代码进行兼容和互换而提供的;
Set<Object>
是个参数化类型,所以可以包含任何对象类型的一个集合;Set<?>
则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set则是原生态类型,它脱离了泛型安全性检查规则。前两个是安全的,最后一种不安全。